import type { Plugin } from 'vite'
import fs from 'node:fs'
import path from 'node:path'
import MagicString from 'magic-string'
import { normalizePath } from 'vite'
import { readJSONFile, writeJSONFile } from './json-file'

interface IBase64Config {
  /** @description  自定义需要转化为base64的图片后缀 */
  base64Suffix?: 'base64'
  /** @description  自定义需要上传的图片后缀 */
  networkSuffix?: 'network'
  /** @description  特定平台转化为base64，默认值：['mp-weixin'] */
  plat?: string[]
  /** @description  匹配特定文件，避免所有文件类型都匹配 */
  fileReg?: RegExp
  /** @description  项目根目录 */
  root?: string
  /** @description 资源清单文件名 */
  mainifest?: string
  /** @description 图片中路径别名 */
  alias?: Record<string, string>
  uploadImp?: (filePath: string, relativePath: string) => Promise<string>
}

// eslint-disable-next-line node/prefer-global/process
export function img2base64OrNetwork({ fileReg = /.(vue|uvue|nvue|css|scss|less)$/, root = process.cwd(), alias = {}, mainifest = '.mainifest', base64Suffix = 'base64', networkSuffix = 'network', plat = ['h5', 'mp-weixin'], uploadImp } = {} as IBase64Config): Plugin {
  if (uploadImp && typeof uploadImp !== 'function') {
    throw new Error('uploadImp must be a function')
  }
  alias = Object.assign({
    '@': root,
    '@/': `${root}/`,
    '~': root,
    '~/': `${root}/`
  }, alias)
  // image直接引入或者import引入正则
  const base64Reg = new RegExp(`\\.(png|jpg|jpeg|svg|gif|webp)\\?${base64Suffix}$`)
  const networkReg = new RegExp(`\\.(png|jpg|jpeg|svg|gif|webp)\\?${networkSuffix}$`)
  // 匹配样式或模板中的url(xxx)形式
  // const urlRegex = /url\(['"]?(.*?)['"]?\)\s*/g
  const suffixPattern = `${base64Suffix}|${networkSuffix}`
  const urlRegex = new RegExp(
    `(?:src=["']|url\\(["']?)([^"'\\s]+?\\.(?:png|jpe?g|gif|svg|webp))\\?(${suffixPattern})(?=["')])`,
    'gi'
  )
  const fileMap: Map<string, any> = new Map()
  root = normalizePath(root)
  return {
    name: 'vite-plugin-img-to-base64',
    enforce: 'pre',
    async transform(code: string, id: string) {
      // eslint-disable-next-line node/prefer-global/process
      if (process.env.UNI_PLATFORM && !plat.includes(process.env.UNI_PLATFORM || '')) return
      // 不是import引入的图片和image引入的，比如样式引入背景图，则抽取代码单独转化
      if (!id.includes(root) || id.includes('node_modules')) return

      if (base64Reg.test(id)) {
        const uid = genUid('', id)
        const cacheFile = fileMap.get(uid)
        if (cacheFile) {
          return toCodeSourceMap(cacheFile, id)
        }
        // import引入的图片和image引入的
        const removeUrlSuffix = id.replace(`?${base64Suffix}`, '')

        const base64 = await file2Base64(removeUrlSuffix)
        const tempCode = `export default "${base64}"`
        fileMap.set(uid, tempCode)
        return toCodeSourceMap(tempCode, id)
      }
      else if (networkReg.test(id)) {
        const uid = genUid('', id)
        const cacheFile = fileMap.get(uid)

        if (cacheFile) {
          return toCodeSourceMap(cacheFile, id)
        }

        const networkUrl = await recordNetUrl({
          imgUrl: id,
          suffix: networkSuffix,
          fileUrl: id,
          root,
          mainifest,
          alias,
          uploadImp
        })
        if (!networkUrl) return
        const tempCode = `export default "${networkUrl}"`
        fileMap.set(uid, tempCode)
        return toCodeSourceMap(tempCode, id)
      }

      const fileUrl = id.replace(/\?.*$/, '')
      if (fileReg.test(fileUrl)) {
        let match
        const toNetFileList: string[] = []
        // eslint-disable-next-line no-cond-assign
        while ((match = urlRegex.exec(code)) !== null) {
          const imgUrl: string = (`${match[1]}?${match[2]}`) || ''
          if (base64Reg.test(imgUrl)) {
            const uid = genUid(root, imgUrl)
            const cacheFile = fileMap.get(uid)
            if (cacheFile) {
              code = code.replace(imgUrl, cacheFile)
            }
            else {
              const removeUrlSuffix = imgUrl.replace(`?${base64Suffix}`, '')
              const { fullPath } = getImgFilePath(root, fileUrl, removeUrlSuffix, alias)
              const base64 = await file2Base64(fullPath)
              fileMap.set(uid, base64)
              code = code.replace(imgUrl, base64)
            }
          }
          else if (networkReg.test(imgUrl)) {
            const uid = genUid(root, imgUrl)
            const cacheFile = fileMap.get(uid)
            if (cacheFile) {
              code = code.replace(imgUrl, cacheFile)
            }
            else {
              toNetFileList.push(imgUrl)
            }
          }
        }
        if (toNetFileList.length > 0) {
          const networkListRes = await Promise.allSettled(toNetFileList.map((imgUrl) => recordNetUrl({
            imgUrl,
            suffix: networkSuffix,
            fileUrl,
            root,
            mainifest,
            alias,
            uploadImp
          })))
          for (let i = 0; i < networkListRes.length; i++) {
            const res = networkListRes[i]
            const imgUrl = toNetFileList[i]
            if (res.status === 'fulfilled') {
              const { value: networkUrl } = res
              if (networkUrl) {
                const uid = genUid(root, imgUrl)
                fileMap.set(uid, networkUrl)
                code = code.replace(imgUrl, networkUrl)
              }
            }
          }
        }
        return toCodeSourceMap(code, id)
      }
    },
    // handleHotUpdate(ctx) {
    //   const { file, server, modules } = ctx;
    //   // 过滤文件
    //   if (!fileReg.test(file)) return
    //   // 找到引入的 vue 文件
    //   const relationId = fileMap.get(file) as string;
    //   // 找到该 vue 文件的 moduleNode
    //   const relationModules = []
    //   const mods = server.moduleGraph.getModulesByFile(relationId)
    //   if (mods) {
    //     for (const module of mods) {
    //       // 处理模块
    //       relationModules.push(module)
    //     }
    //     const relationModule = relationModules[0]
    //     // 发送 websocket 消息，进行单文件热重载
    //     server.ws.send({
    //       type: 'update',
    //       updates: [
    //         {
    //           type: 'js-update',
    //           path: relationModule.file!,
    //           acceptedPath: relationModule.file!,
    //           timestamp: new Date().getTime()
    //         }
    //       ]
    //     })
    //     // 指定需要重新编译的模块
    //     return [...modules, relationModule]
    //   }
    // }
  }
}
interface NetWork {
  imgUrl: string
  suffix: string
  fileUrl: string
  root: string
  mainifest: string
  alias: Record<string, string>
  uploadImp?: (filePath: string, relativePath: string) => Promise<string>
}
function toCodeSourceMap(code: string, id: string) {
  return {
    code,
    map: null
  }
  // const s = new MagicString(code)
  // return {
  //   code: s.toString(),
  //   map: s.generateMap({
  //     source: id,
  //     includeContent: true,
  //     file: `${id}.map`
  //   })
  // }
}

async function recordNetUrl({ imgUrl, suffix, fileUrl, root, mainifest, alias, uploadImp }: NetWork) {
  const removeUrlSuffix = imgUrl.replace(`?${suffix}`, '')
  const { fullPath, relativePath } = getImgFilePath(root, fileUrl, removeUrlSuffix, alias)

  const fileMainifest = path.join(path.dirname(fullPath), mainifest)
  if (!fs.existsSync(fileMainifest)) {
    try {
      const networkUrl = await uploadImp!(fullPath, relativePath)
      if (!networkUrl) return
      writeJSONFile(fileMainifest, { [relativePath]: networkUrl })
      return networkUrl
    }
    catch (error) {
      return
    }
  }
  const fileJson = readJSONFile(fileMainifest)
  const networkUrl = fileJson[relativePath]
  if (networkUrl) return networkUrl
  try {
    const networkUrl = await uploadImp!(fullPath, relativePath)
    if (!networkUrl) return
    fileJson[relativePath] = networkUrl
    writeJSONFile(fileMainifest, fileJson)
    return networkUrl
  }
  catch (error) {

  }
}

async function file2Base64(id: string) {
  // //获取文件数据
  // const stat = await fs.promises.stat(id);
  // // 如果文件大小超过限制则不转换，太大的图片转换为base64不划算
  // if (stat.size > limit) return
  const buffer = await fs.promises.readFile(id)
  const base64 = buffer.toString('base64')
  // 返回转换后的代码
  return `data:image/${id.split('.').pop()};base64,${base64}`.replace(/[\r\n]/g, '')
}

function getImgFilePath(root: string, relative = '', filePath = '', alias: Record<string, string>): {
  fullPath: string
  relativePath: string
} {
  if (filePath.startsWith('/')) {
    const fullPath = normalizePath(path.join(root, filePath))

    return {
      fullPath,
      relativePath: fullPath.replace(root, '')
    }
  }

  const aliasItem = Object.keys(alias).find(v => filePath.startsWith(v))
  if (aliasItem) {
    /**
     * alis {
     *  '@/': 'g:/test/static/'
     *  }
     *
     */
    // demo @/static/a.png -> g:/test/static/a.png
    const fullPath = normalizePath(path.join(alias[aliasItem], filePath.replace(aliasItem, '')))
    return {
      fullPath,
      relativePath: fullPath.replace(root, '')
    }
  }

  const baseDir = path.dirname(relative)
  const fullPath = normalizePath(path.resolve(baseDir, filePath))

  return {
    fullPath,
    relativePath: fullPath.replace(root, '')
  }
}

function genUid(root: string, fileName: string) {
  return (root || '') + fileName
}

export default img2base64OrNetwork
